package yb.ecp.fast.infra.security;

import com.alibaba.cloud.commons.lang.StringUtils;
import com.alibaba.nacos.shaded.com.google.common.base.Joiner;
import com.alibaba.nacos.shaded.com.google.common.base.Throwables;
import com.alibaba.nacos.shaded.com.google.common.collect.Lists;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.reactivestreams.Publisher;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Service;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import yb.ecp.fast.infra.infra.ActionResult;
import yb.ecp.fast.infra.infra.FastJsonUtil;

import javax.annotation.Resource;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR;

@Slf4j
@Service
@RefreshScope
public class LoginFilter implements GlobalFilter, Ordered {
    private static final Joiner joiner = Joiner.on("");
    public static final String REDIS_KEY_PREFIX = "xgw:";
    public static final Charset UTF_8 = StandardCharsets.UTF_8;

    @Value("${fast.auth.login.loginUrls}")
    private List<String> loginUrls;

    @Value("${fast.auth.login.sessionTime:3000}")
    private Long sessionTime;

    @Resource(name = "gwRedisTemplate")
    private RedisTemplate<String, Object> redisTemplate;

    @SneakyThrows
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        log.info("login filter starter");

        String path = exchange.getRequest().getURI().getPath();
        if (shouldFilter(path)) {
            ServerHttpResponse originalResponse = exchange.getResponse();
            DataBufferFactory bufferFactory = originalResponse.bufferFactory();
            ServerHttpResponseDecorator response = new ServerHttpResponseDecorator(originalResponse) {
                @Override
                public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
                    if (HttpStatus.OK.equals(getStatusCode()) && body instanceof Flux) {
                        String contentType = exchange.getAttribute(ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR);
                        if (StringUtils.isNotBlank(contentType) && contentType.contains("application/json")) {
                            Flux<? extends DataBuffer> fluxBody = Flux.from(body);
                            return super.writeWith(fluxBody.buffer().map(dataBuffers -> {
                                List<String> chunks = Lists.newArrayList();
                                dataBuffers.forEach(buffer -> {
                                    try {
                                        byte[] content = new byte[buffer.readableByteCount()];
                                        buffer.read(content);
                                        chunks.add(new String(content, UTF_8));
                                    } catch (Exception e) {
                                        log.error("Response buffer processing failed: {}", Throwables.getStackTraceAsString(e));
                                    } finally {
                                        DataBufferUtils.release(buffer);
                                    }
                                });

                                String responseData = joiner.join(chunks);
                                log.debug("Processed response data"); // 移除敏感数据日志

                                if (StringUtils.isNotBlank(responseData)) {
                                    try {
                                        ActionResult<Map<String, Object>> result = FastJsonUtil.parse(responseData, ActionResult.class);
                                        if (result != null && result.getValue() != null) {
                                            Map<String, Object> authInfo = createAuthInfo(result.getValue());
                                            result.setValue(authInfo);
                                            responseData = FastJsonUtil.toJSONString(result);
                                            storeAuthToken(authInfo.get("ScAuth").toString(), authInfo.get("userId"));
                                        }
                                    } catch (Exception e) {
                                        log.warn("Failed to process auth response: {}", e.getMessage());
                                    }
                                }

                                byte[] processedContent = responseData.getBytes(UTF_8);
                                originalResponse.getHeaders().setContentLength(processedContent.length);
                                return bufferFactory.wrap(processedContent);
                            }));
                        }
                    }
                    return super.writeWith(body);
                }

                @Override
                public Mono<Void> writeAndFlushWith(Publisher<? extends Publisher<? extends DataBuffer>> body) {
                    return writeWith(Flux.from(body).flatMapSequential(p -> p));
                }
            };
            return chain.filter(exchange.mutate().response(response).build());
        }
        return chain.filter(exchange)
                .then(Mono.fromRunnable(
                        () -> log.info("Global Post Filter Execute...{}", path)
                ));
    }

    private Map<String, Object> createAuthInfo(Object userId) {
        Map<String, Object> auth = new HashMap<>(2);
        auth.put("userId", userId);
        auth.put("ScAuth", UUID.randomUUID().toString());
        return auth;
    }

    private void storeAuthToken(String token, Object userId) {
        try {
            redisTemplate.opsForValue().set(
                    REDIS_KEY_PREFIX + token,
                    userId,
                    Duration.ofSeconds(sessionTime)
            );
        } catch (Exception e) {
            log.error("Failed to store auth token: {}", e.getMessage());
        }
    }

    private boolean shouldFilter(String requestUri) {
        return loginUrls.stream().anyMatch(requestUri::matches);
    }

    @Override
    public int getOrder() {
        return -1;
    }
}
